<?php

namespace App\Services;

use App\Exceptions\ApiException;
use App\Format\UserFormat;
use App\Models\Chapter;
use App\Models\ConsumptionLog;
use App\Models\CouponLog;
use App\Models\Novel;
use App\Models\ReadLogOffline;
use App\Models\Sms;
use App\Models\User;
use App\Models\UserReadLog;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Str;

class UserService
{
    const USER_TOKEN_PRE = 'user_token_%s';
    const USER_VCODE_PRE = 'user_vcode_%s';
    const USER_HEAD_PIC = 'http://image.hanlingsha.love/lwa/headpic/B85A31EB64F482EF44CBDEB6FF49C1C5.jpg';
    const REG_GIVE_COUPON = 2000;

    public function __construct()
    {
    }

    public function getVcodeForLogin($phone)
    {
        $user = User::query()->where('phone', $phone)->first();
        if (! $user) {
            throw new ApiException('账号不存在', 1);
        }
        $vcode = $this->generVcode();
        $this->setVcode($vcode, $user->id);
        return $vcode;
    }

    public function login($phone, $passwordHash)
    {
        $user = User::query()->where('phone', $phone)->first();
        $userId = $user->id;
        $vcode = $this->getVcode($userId);
        if (! $this->checkVcode($userId, $vcode)) {
            throw new ApiException('验证失败', 1);
        }
        $user = User::find($userId);
        if (md5($user->password . $vcode) != $passwordHash) {
            throw new ApiException('密码错误', 1);
        }
        $token = $this->generToken();
        $this->setToken($token, $userId);
        return [
            'userId' => $userId,
            'token'  => $token,
        ];
    }

    public function logout($userId)
    {
        $this->desToken($userId);
        return true;
    }

    public function checkLogin($userId, $token)
    {
        return $this->getToken($userId) == $token;
    }

    public function reg($phone, $code, $userName, $password)
    {
        $sms = Sms::query()->where('phone', $phone)
            ->where('status', Sms::STATUS['send_success'])
            ->where('code', $code)->first();

        if (empty($sms)) {
            throw new ApiException('验证码错误', 1);
        }

        if (time() - strtotime($sms->send_time) > $sms->available_time) {
            throw new ApiException('验证码过期', 1);
        }

        $sms->status = Sms::STATUS['used'];
        $sms->save();

        $user = User::query()->create([
            'user_name' => $userName,
            'password' => md5($password),
            'headpic' => self::USER_HEAD_PIC,
            'email' => '',
            'coupon' => self::REG_GIVE_COUPON,
        ]);

        $token = $this->generToken();
        $this->setToken($token, $user->id);
        return [
            'userId' => $user->id,
            'token' => $token,
        ];
    }

    public function profile($userId)
    {
        return toCamelCaseKey(User::query()->select('id as user_id', 'headpic', 'user_name', 'coupon')->find($userId)->toArray());
    }

    public function changeUser($userId, UserFormat $userFormat)
    {
        $user = User::query()->find($userId);
        $user->update($userFormat->toArrayNotNull());
        $user->save();
        return true;
    }

    public function recordUserReadLog($userId, $novelId, $chapterId)
    {
        return UserReadLog::query()->firstOrCreate([
            'user_id' => $userId,
            'novel_id' => $novelId,
            'chapter_id' => $chapterId,
        ], [
            'read_time' => now(),
        ]);
    }

    public function recordUserReadLogOffline($userId, $novelId, $chapterId, $orderNum)
    {
        return ReadLogOffline::query()->updateOrCreate([
            'user_id' => $userId,
            'novel_id' => $novelId,
            'chapter_id' => $chapterId,
            'order_num' => $orderNum,
        ], [
            'read_time' => now(),
        ]);
    }

    public function getUserConsumptionLog($userId, $page = 1)
    {
        $logs = ConsumptionLog::query()->where('user_id', $userId)
            ->select('id as consumption_log_id', 'id', 'user_id', 'novel_id', 'chapter_id', 'coupon', 'created_at as add_time')
            ->orderBy('id', 'desc')
            ->limit(20)
            ->skip(20 * ($page - 1))
            ->get();
        if ($logs->count() == 0) {
            return [];
        }
        $result = $this->fillNovelAndChapter($logs);
        return toCamelCaseKey($result);
    }

    public function getUserReadLog($userId)
    {
        $logs = ReadLogOffline::query()->where('user_id', $userId)
            ->select('user_id', 'novel_id', 'order_num', 'read_time', 'chapter_id')
            ->orderBy('id', 'desc')
            ->get();
        if ($logs->count() == 0) {
            return [];
        }

        $result = $this->fillNovelAndChapter($logs);
        return toCamelCaseKey($result);
    }

    public function getConsumptionLog($userId, $page = 1)
    {
        $logs = ConsumptionLog::query()->where('user_id', $userId)
            ->skip(($page - 1) * 50)
            ->limit(50)
            ->orderBy('id', 'desc')
            ->select('*', 'id as consumptionLogId')
            ->get();

        $novelIds = $logs->pluck('novel_id');
        $chapterIds = $logs->pluck('chapter_id');

        $novels = Novel::query()->whereIn('id', $novelIds)->select('id', 'novel_name')->get()->toArray();
        $chapter = Chapter::query()->whereIn('id', $chapterIds)->select('id', 'chapter_name')->get()->toArray();

        $novelMap = array_column($novels, null, 'id');
        $chapterMap = array_column($chapter, null, 'id');

        $logs->each(function (&$log) use ($novelMap, $chapterMap) {
            $novelId = $log->novel_id;
            $chapterId = $log->chapter_id;
            $log['novel_name'] = $novelMap[$novelId]['novel_name'] ?? '';
            $log['chapter_name'] = $chapterMap[$chapterId]['chapter_name'] ?? '';
        });

        return toCamelCaseKey($logs->toArray());
    }

    public function sendDevUserCoupon($phone, $coupon = 2000)
    {
        $user = User::query()->where('phone', $phone)->first();
        $user->coupon = $user->coupon + $coupon;
        $user->save();
        CouponLog::query()->create([
            'user_id' => $user->id,
            'coupon' => $coupon,
            'type' => 'D',
        ]);
        return true;
    }

    private function fillNovelAndChapter($logs)
    {
        $novelIds = $logs->pluck('novel_id');
        $chapterIds = $logs->pluck('chapter_id');
        $novels = Novel::query()->whereIn('id', $novelIds)->select('id', 'novel_name')->get()->toArray();
        $chapters = Chapter::query()->whereIn('id', $chapterIds)->select('id', 'chapter_name')->get()->toArray();
        $novelMap = array_column($novels, null, 'id');
        $chapterMap = array_column($chapters, null, 'id');
        $result = $logs->toArray();
        array_walk($result, function (&$row) use ($novelMap, $chapterMap) {
            $novelId = $row['novel_id'];
            $chapterId = $row['chapter_id'];
            $row['novel_name'] = $novelMap[$novelId]['novel_name'] ?? '';
            $row['chapter_name'] = $chapterMap[$chapterId]['chapter_name'] ?? '';
        });
        return $result;
    }

    private function setToken($token, $userId)
    {
        Redis::set(sprintf(self::USER_TOKEN_PRE, $userId), $token);
    }

    private function setVcode($vcode, $userId)
    {
        Redis::set(sprintf(self::USER_VCODE_PRE, $userId), $vcode);
    }

    private function generVcode()
    {
        return rand(10000, 99999);
    }

    private function generToken()
    {
        return Str::random();
    }

    private function checkVcode($userId, $vcode)
    {
        return $this->getVcode($userId) == $vcode;
    }

    private function getVcode($userId)
    {
        return Redis::get(sprintf(self::USER_VCODE_PRE, $userId));
    }

    public function getToken($userId)
    {
        return Redis::get(sprintf(self::USER_TOKEN_PRE, $userId));
    }

    private function desToken($userId)
    {
        return Redis::del(sprintf(self::USER_TOKEN_PRE, $userId));
    }
}
